/*
 * File         : task.c
 * This file is part of POSIX-RTOS
 * COPYRIGHT (C) 2015 - 2016, DongHeng
 *
 * Change Logs:
 * DATA             Author          Note
 * 2015-11-23       DongHeng        create
 */
#include "task.h"
#include "list.h"
#include "string.h"
#include "debug.h"
#include "stdlib.h"
#include "pthread.h"

/*@{*/

/* task create limited parameter */
#ifndef TASK_STACK_BYTES_MIN
    #define TASK_STACK_BYTES_MIN                64
#endif

/* task create default parameter */
#ifndef USR_INIT_TYPE_DEFAULT
    #define USR_INIT_TYPE_DEFAULT               TASK_TYPE_USER
#endif
#ifndef USR_INIT_PRIO_DEFAULT
    #define USR_INIT_PRIO_DEFAULT               16
#endif
#ifndef USR_INIT_TICK_DEFAULT
    #define USR_INIT_TICK_DEFAULT               1
#endif
#ifndef USR_INIT_STK_ADDR_DEFAULT
    #define USR_INIT_STK_ADDR_DEFAULT           NULL
#endif
#ifndef USR_INIT_STK_BYTES_DEFAULT
    #define USR_INIT_STK_BYTES_DEFAULT          512
#endif

#define TASKSCHED_SET_STATE(a, b)               (a)->status = (b)

/******************************************************************************/

/* task scheduler structure description */
struct tasksched
{
	uint32_t            task_ready_group;
    list_t              task_ready_table[TASK_READY_GROUP_MAX];

    list_t              task_sleep_list;
    list_t              task_delete_list;

    list_t              task_list;
    
    phys_reg_t          lock;
};

#define TASKLIST_ENTER_CRITICAL() pthread_mutex_lock(&tasklist_mutex)
#define TASKLIST_EXIT_CRITICAL()  pthread_mutex_unlock(&tasklist_mutex)

/*@}*/

/*@{*/

/*
 * thread global list protection lock, using mutex means that
 * no one can create or delete the thread in ISR
 */
STATIC DEFINE_MUTEX(tasklist_mutex);

/*
 * task global scheduler protection lock, using spinlock means that
 * the current CPU is disable interrpt, and wait for the lock to be free
 */
STATIC DEFINE_SPINLOCK(tasksched_spinlock);

task_t *current_task;

/*
 *system scheduler structure 
 */
STATIC struct tasksched sched KERNEL_SECTION;
/*@}*/

/*@{*/

INLINE void tasksched_remove_task(task_t *task)
{
    list_remove_node(&task->list);
    if (list_is_empty(&sched.task_ready_table[task->cur_prio]))
    	sched.task_ready_group &= ~(1 << (task->cur_prio));
}

/**
 * This function will get the highest priority task
 *
 * @param read_group the read tasks record bit group
 *
 * @return task number which has the highest priority
 */
INLINE uint16_t tasksched_get_highest_ready_group(uint32_t read_group)
{
	uint16_t offset = 0;
    /* scheduler task priority mapped table */
    STATIC OS_RO uint8_t tasksched_priority_remap_table[OS_U8_MAX + 1] =
    {
        0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,

        4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,

        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,

        6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
        6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
        6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
        6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,

        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    };

    if (read_group & 0xff000000)
    {
        read_group >>= 24;
        offset = 24;
    }
    else if (read_group & 0x00ff0000)
    {
        read_group >>= 16;
        offset = 16;
    }
    else if (read_group & 0x0000ff00)
    {
        read_group >>= 8;
        offset = 8;
    }

    return tasksched_priority_remap_table[read_group] + offset;
}

/**
 * This function will start the operation system scheduler
 */
void tasksched_start(void)
{
    uint32_t task_ready_group_num = tasksched_get_highest_ready_group(sched.task_ready_group);

    current_task = LIST_HEAD_ENTRY(&sched.task_ready_table[task_ready_group_num],
                                   task_t,
                                   list);
    
    tasksched_remove_task(current_task);

    context_switch_to((phys_reg_t)&current_task->sp);
}

/**
 * This function will start the operation system scheduler
 */
void tasksched_switch_task(void)
{
    phys_reg_t temp;
    uint32_t task_ready_group_num;
    task_t *to_task;

    if (sched.lock)
        return ;
    
    /* suspend the hardware interrupt for the preparing for context switching */
    temp = interrupt_suspend();
    pthread_spin_lock(&tasksched_spinlock);

    task_ready_group_num = tasksched_get_highest_ready_group(sched.task_ready_group);

    to_task = LIST_HEAD_ENTRY(&sched.task_ready_table[task_ready_group_num],
    		                  task_t,
                              list);

    /* we do context switching if current highest priority task being changed */
    if (to_task != current_task)
    {
    	task_t *from_task;

        from_task = current_task;
        current_task = to_task;

        SCHED_DEBUG(SCHED_DEBUG_ENABLE, "from task [0x%08x] to [0x%08x]\r\n",
        								from_task, to_task);

        tasksched_remove_task(to_task);
        
        if (TASK_STATE_READY == from_task->status)
            tasksched_set_task_ready(from_task);
        
        context_switch((phys_reg_t)&from_task->sp, (phys_reg_t)&to_task->sp);

        SCHED_DEBUG(SCHED_DEBUG_ENABLE, "task switched.\r\n");
    }

    /* recover the system or user interrupt status */
    pthread_spin_unlock(&tasksched_spinlock);
    interrupt_recover(temp);
}

/**
 * This function will wake up the sleeping task if it is timeout
 */
INLINE void tasksched_wakeup_sleep_task(void)
{
	task_t *ptask, *p;

    LIST_FOR_EACH_ENTRY_SAFE(ptask,
                             p,
                             &sched.task_sleep_list,
							 task_t,
                             list)
    {
        /* active the suspend task and put it on ready group */
    	ptask->sleep_ticks--;

        /* check the sleeping time */
        if (!ptask->sleep_ticks)
        {
            /* wake up the task */
        	tasksched_set_task_ready(ptask);
        }
    }
}

/**
 * This function will check if the highest priority of task scheduler changed
 */
INLINE void tasksched_proc_current_task(void)
{
    --current_task->cur_ticks;
    if (!current_task->cur_ticks)
    {
        /* remove the task from the task-ready group head and insert it to tail */
    	tasksched_set_task_ready(current_task);
    }
}

/**
 * This function is the entry function of scheduler timer timeout
 */
void sched_proc(void)
{
    /* check and handle current task */
    tasksched_proc_current_task();

    /* check and wake up the sleeping task */
    tasksched_wakeup_sleep_task();

    /* switch to the task which has the highest priority */
    tasksched_switch_task();
}

/**
 * This function will initialize the system scheduler
 *
 * @return the result of initialization the scheduler
 */
void tasksched_init(void)
{
    int i;

    /* clear the scheduler structure */
    memset(&sched, 0, sizeof(struct tasksched));

    /* initialize all list of scheduler */
    for( i = 0; i < TASK_READY_GROUP_MAX; i++ )
    {
        list_init( &sched.task_ready_table[i] );
    }
    list_init(&sched.task_list);
    list_init(&sched.task_sleep_list);
    list_init(&sched.task_delete_list);
}

/**
 * This function will insert the task into the global task list
 *
 * @param task the task point to be registered
 */
void tasksched_insert_task(task_t *task)
{
    TASKLIST_ENTER_CRITICAL();
    list_insert_tail(&sched.task_list, &task->tlist); 
    TASKLIST_EXIT_CRITICAL();
}

/**
 * This function will insert the task into the task-ready list and set the group
 *
 * @param task the task point to be handled
 */
void tasksched_set_task_ready(task_t *task)
{
    list_remove_node(&task->list);

    list_insert_tail(&sched.task_ready_table[task->cur_prio], &task->list);
    sched.task_ready_group |= (1 << task->cur_prio);
    task->status = TASK_STATE_READY;
}

/**
 * This function will suspend the task and move from the task-ready table
 * and reset the group scheduler_suspend_task
 *
 * @param task the task point to be handled
 */
void tasksched_set_task_suspend(task_t *task)
{  
    tasksched_remove_task(task);

    TASKSCHED_SET_STATE(task, TASK_STATE_SUSPEND);
}

/**
 * his function will let the task sleep
 *
 * @param task the task point to be handled
 */
void tasksched_set_task_sleep(task_t *task)
{
    tasksched_remove_task(task);

    list_insert_tail(&sched.task_sleep_list, &task->list);
    
    TASKSCHED_SET_STATE(task, TASK_STATE_SLEEP);
}

/**
 * This function will let the task closed
 *
 * @param task the task point to be handled
 */
void tasksched_set_task_close(task_t *task)
{
    tasksched_remove_task(task);

    TASKSCHED_SET_STATE(task, TASK_STATE_CLOSED);
}

/**
  * This function will delete the task from the scheduler table
  *
  * @param task the task point to be handled
  *
  * return none
  */
void tasksched_delete_task(task_t *task)
{
    /* remove the task from the global task group list */
    TASKLIST_ENTER_CRITICAL();
    list_remove_node(&task->tlist);
    TASKLIST_EXIT_CRITICAL();

    /* remove the task from the ready task group list */
    tasksched_remove_task(task);
    
    TASKSCHED_SET_STATE(task, TASK_STATE_CLOSED);
    
    list_insert_tail(&sched.task_delete_list, &task->list);
}

/**
 * tasksched_yield - This function will let the current task jump into the tail
 *               of the task-ready list
 */
void tasksched_yield(void)
{
    /* remove the current task from the ready task
       and put it to the tail of the ready group */
    tasksched_set_task_ready(__get_current_task());
    
    tasksched_switch_task();
}

/**
  * the function will report the current task
  */
void tasksched_status_report(void)
{
	task_t *task = __get_current_task();

	printk("current task id is %p.\n", task);
}

/*
 * __ptask_exit_entry - the function is the entry of the task exiting
 *
 * @param arg the return data of the task
 */
STATIC void task_exit_entry(void *arg)
{
	task_t *task = __get_current_task();
    
	tasksched_delete_task(task);
}

/*
 * task_create - create a task and set its state initialized
 *
 * @param entry    task function entry
 * @param name     task name
 * @param stk      task stack point, computed by bytes
 * @param stk_size task stack bytes
 * @param param    task task function parameter
 * @param priority task priority
 * @param tick     task running ticks slice
 *
 * @return the task handle
 */
task_t* task_create(void (*entry)(void *),
					const char *name,
					char *stk,
					size_t stk_size,
					void *arg,
					task_prio_t priority,
					task_tick_t tick,
					task_type_t type)
{
	task_t *task;
	extern char* task_hw_stack_init(void entry(void *),
			                        void *paramter,
									char *stack,
			                        phys_addr_t size,
			                        void exit(void *));

	if (!entry)
		goto error;

	if (type != TASK_TYPE_USER &&
		type != TASK_TYPE_IDLE &&
		type != TASK_TYPE_KERNEL)
		goto error;

	task = malloc(sizeof(task_t));
	if (!task)
		goto error;

	if (!stk_size)
		stk_size = USR_INIT_STK_BYTES_DEFAULT;

	if (!stk) {
		stk = malloc(stk_size);
		if (!stk)
			goto stk_err;
	}

	if (!priority)
		priority = USR_INIT_PRIO_DEFAULT;
	if (!tick)
		tick = USR_INIT_TICK_DEFAULT;

	list_init(&task->list);
	list_init(&task->tlist);
    
	task->start_routine = entry;
	task->arg = arg;

	task->stk_addr = stk;
	task->stk_size = stk_size;

	task->init_prio = priority;
	task->cur_prio = priority;
	task->prio_mask = 1 << priority;

	task->init_ticks = tick;
	task->cur_ticks = tick;

	task->status = TASK_STATE_INIT;
	task->type = type;

	task->sp = task_hw_stack_init(entry, arg, stk, stk_size, task_exit_entry);
    
    tasksched_insert_task(task);
    
    tasksched_set_task_ready(task);

	return task;

stk_err:
	free(task);
error:
	return NULL;
}

void __tasksched_disable (void)
{
    atomic_add(&sched.lock, 1);
}

void __tasksched_enable(void)
{
    atomic_sub(&sched.lock, 1);
}

/*@}*/
